package cn.seaboot.admin.goal;

import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.exception.BizException;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * E.G.:
 * List<Map<String,Object>> tree = new GoalBuilder()
 * .setParent(list)
 * .setChild(list)
 * .groupBy()
 * .getByMinPid();
 * <p>
 * 基于 Java8 封装的树形结构组装工具。
 * <p>
 * 其中，如果父节点如果为 null，通常属于根节点，算法上也是这么判断的。
 *
 * @author Mr.css
 * @version 2022-11-28 8:24
 */
public class GoalBuilder<T> {

    /**
     * 父级
     */
    private List<GoalNode<T>> parent;

    /**
     * 子级
     */
    private List<GoalNode<T>> child;


    /**
     * 数据映射
     */
    private Function<T, GoalNode<T>> mapper;

    /**
     * 数据映射函数，将数据转换成 GoalNode
     *
     * @param mapper 数据映射函数
     * @return this
     */
    public GoalBuilder<T> setMapper(Function<T, GoalNode<T>> mapper) {
        this.mapper = mapper;
        return this;
    }

    /**
     * 父节点列表
     *
     * @param p parent-list
     * @return this
     */
    public GoalBuilder<T> setParent(List<GoalNode<T>> p) {
        this.parent = p;
        return this;
    }

    /**
     * 子节点列表
     *
     * @param child child-list
     * @return this
     */
    public GoalBuilder<T> setChild(List<GoalNode<T>> child) {
        this.child = child;
        return this;
    }

    /**
     * 映射父节点列表
     *
     * @param p parent-list
     * @return this
     */
    public GoalBuilder<T> mapParent(List<T> p) {
        this.parent = p.stream().map(mapper).collect(Collectors.toList());
        return this;
    }

    /**
     * 映射成子节点列表
     *
     * @param child child-list
     * @return this
     */
    public GoalBuilder<T> mapChild(List<T> child) {
        this.child = child.stream().map(mapper).collect(Collectors.toList());
        return this;
    }

    public List<GoalNode<T>> getParent() {
        return parent;
    }

    public List<GoalNode<T>> getChild() {
        return child;
    }

    public Function<T, GoalNode<T>> getMapper() {
        return mapper;
    }

    /**
     * 数据分组
     *
     * @return GoalBuilder
     */
    public GoalBuilder<T> groupBy() {
        if (CommonUtils.isEmpty(parent) && CommonUtils.isEmpty(child)) {
            throw new IllegalArgumentException("Child list and parent list are both empty!");
        }
        if (CommonUtils.isEmpty(parent)) {
            this.parent = this.child;
        }
        if (CommonUtils.isEmpty(child)) {
            this.child = this.parent;
        }

        for (GoalNode<T> p : parent) {
            for (GoalNode<T> c : child) {
                // 此处可能发生空指针，注意初始化集合
                if (p.getId().equals(c.getPid())) {
                    // 子节点指向父节点
                    c.setParent(p);

                    // 父节点指向子节点
                    List<GoalNode<T>> list = p.getChildren();
                    if (CommonUtils.isEmpty(list)) {
                        list = new ArrayList<>();
                        p.setChildren(list);
                    }
                    list.add(c);
                }
            }
        }
        return this;
    }

    /**
     * 如果已知要取的节点，直接按照节点pId进行获取
     *
     * @param pid 值可能为空，数据库可能用null值表示顶级节点
     * @return tree
     */
    public List<GoalNode<T>> getByPid(@Nullable Object pid) {
        List<GoalNode<T>> res = new ArrayList<>();
        if (pid == null) {
            for (GoalNode<T> p : parent) {
                if (p.getPid() == null) {
                    res.add(p);
                }
            }
        } else {
            for (GoalNode<T> p : parent) {
                if (pid.equals(p.getPid())) {
                    res.add(p);
                }
            }
        }
        return res;
    }

    /**
     * 从结果集中取出 pid 最小的数据
     * <p>
     * 特例：如果存在 pid 为 null 的数据，则将该数据，作为最小的数据
     *
     * @return tree
     */
    public List<GoalNode<T>> getByMinPid() {
        String min = parent.get(0).getPid();
        String pid;
        for (GoalNode<T> p : parent) {
            pid = p.getPid();
            if (pid == null) {
                return this.getByPid(null);
            } else {
                if (pid.compareTo(min) < 0) {
                    min = pid;
                }
            }
        }
        return this.getByPid(min);
    }

    /**
     * 从组装的结果中取出Pid最大的数据
     * <p>
     * 特例：如果存在 pid 为 null 的数据，则将该数据，作为最大的数据
     *
     * @return tree
     */
    public List<GoalNode<T>> getByMaxPid() {
        String max = parent.get(0).getPid();
        String pid;
        for (GoalNode<T> p : parent) {
            pid = p.getPid();
            if (pid == null) {
                return this.getByPid(null);
            } else {
                if (pid.compareTo(max) > 0) {
                    max = pid;
                }
            }
        }
        return this.getByPid(max);
    }

    /**
     * 随机取出一个元素，反复遍历树，直到取出最顶级的父节点。
     * <p>
     * 树状结构越深，需要遍历的次数越多。
     *
     * @return tree
     */
    public List<GoalNode<T>> getRoot() {
        // 以第一个节点的 pid 作为顶级节点，不断递归检索
        GoalNode<T> top = parent.get(0);

        // 声明一个 list 记录已经查找过的节点，避免出现无限递归
        List<String> list = new ArrayList<>();
        list.add(top.getPid());

        while (top.hasParent()) {
            top = top.getParent();
            if (list.contains(top.getPid())) {
                // 出现无限递归的数据，异常退出
                throw new BizException("Circular dependencies：" + top);
            } else {
                // 继续下一次递归检索
                list.add(top.getPid());
            }
        }
        return this.getByPid(top.getPid());
    }

    /**
     * 重新计算整个目标树的 KPI
     */
    public void summaryValue() {
       summaryValue(this.getRoot());
    }

    /**
     * 重新计算整个目标树的 KPI
     */
    public void summaryValue(List<GoalNode<T>> roots) {
        for (GoalNode<T> c : roots) {
            if(c.hasChildren()){
                summaryNodeValue(c);
            }
        }
    }

    /**
     * 递归计算所有节点的 KPI
     *
     * @param root 根节点
     */
    private void summaryNodeValue(GoalNode<T> root) {
        List<GoalNode<T>> children = root.getChildren();

        // 起始变量
        int val = 0;
        for (GoalNode<T> c : children) {
            if (c.hasChildren()) {
                // 递归处理节点值
                summaryNodeValue(c);
            }

            val += c.getValue();
        }
        root.setValue(val);
    }
}
